package com.code.user.config.token;

import cn.hutool.core.lang.Validator;
import cn.hutool.core.util.StrUtil;
import com.code.common.component.SpringContextHolder;
import com.code.common.exception.ServiceException;
import com.code.common.model.ResponseCode;
import com.code.user.constant.CacheKeyPrefixConst;
import com.code.user.model.AccountDO;
import com.code.user.service.impl.AccountServiceImpl;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.security.authentication.*;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.oauth2.provider.*;
import org.springframework.security.oauth2.provider.token.AbstractTokenGranter;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;

import java.util.LinkedHashMap;
import java.util.Map;

/**
 * Created by IntelliJ IDEA.
 *
 * @author: Yan
 * @date: 2023/4/14
 *
 * 短信验证登录
 */
public class SmsCodeTokenGranter extends AbstractTokenGranter {

    private static final String GRANT_TYPE = "sms";
    private final AuthenticationManager authenticationManager;
    private StringRedisTemplate stringRedisTemplate;
    private AccountServiceImpl accountService;

    public SmsCodeTokenGranter(AuthenticationManager authenticationManager,
                               StringRedisTemplate stringRedisTemplate,
                               AccountServiceImpl accountService,
                               AuthorizationServerTokenServices tokenServices,
                               ClientDetailsService clientDetailsService,
                               OAuth2RequestFactory requestFactory) {
        this(authenticationManager, tokenServices, clientDetailsService, requestFactory, GRANT_TYPE);
        this.stringRedisTemplate = stringRedisTemplate;
        this.accountService = accountService;
    }

    public SmsCodeTokenGranter(AuthenticationManager authenticationManager,
                               AuthorizationServerTokenServices tokenServices,
                               ClientDetailsService clientDetailsService,
                               OAuth2RequestFactory requestFactory,
                               String grantType) {
        super(tokenServices, clientDetailsService, requestFactory, grantType);
        this.authenticationManager = authenticationManager;
    }

    @Override
    protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {
        Map<String, String> parameters = new LinkedHashMap<>(tokenRequest.getRequestParameters());
        // 提交的手机号码
        String username = parameters.get("username");
        // 提交的验证码
        String smsCode = parameters.get("smsCode");

        this.validatorMobile(username);
        this.checkSmsCode(username, smsCode);

        // 根据手机号码查询用户是否注册
        AccountDO searchAccount = accountService.searchAccountByUsername(username);

        DataSourceTransactionManager transactionManager = SpringContextHolder.getBean(DataSourceTransactionManager.class);
        DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
        definition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
        TransactionStatus transaction = transactionManager.getTransaction(definition);

        OAuth2Authentication oAuth2Authentication;

        try {
            // 账号不存在则自动注册
            if (StrUtil.isEmptyIfStr(searchAccount)) {
                accountService.registerAccount(username);
            }

            // 查询账号相关信息
            UserDetails user = accountService.loadUserByUsername(username);

            //调用自定义的token扩展，实现用户认证
            Authentication userAuth = new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());
            ((AbstractAuthenticationToken) userAuth).setDetails(parameters);

            if (userAuth == null && !userAuth.isAuthenticated()) {
                throw new ServiceException(ResponseCode.USER_LOGIN_FAILURE);
            }

            OAuth2Request storedOAuth2Request = this.getRequestFactory().createOAuth2Request(client, tokenRequest);
            oAuth2Authentication = new OAuth2Authentication(storedOAuth2Request, userAuth);
        } catch (Exception e) {
            transactionManager.rollback(transaction);
            throw new ServiceException(ResponseCode.USER_LOGIN_FAILURE);
        }

        transactionManager.commit(transaction);
        return oAuth2Authentication;
    }

    private void checkSmsCode(String username, String code) {
        if (StrUtil.isBlank(code)) {
            throw new ServiceException(ResponseCode.VERIFY_CODE_NOT_EMPTY);
        }

        String cacheKey = CacheKeyPrefixConst.SMS_CODE_PREFIX + username;
        String cacheCodeValue = stringRedisTemplate.opsForValue().get(cacheKey);

        if (StrUtil.isBlank(cacheCodeValue)) {
            throw new ServiceException(ResponseCode.VERIFY_CODE_EXPIRED);
        }

        cacheCodeValue = StrUtil.split(cacheCodeValue, "_")[0];
        if (!StrUtil.equalsIgnoreCase(code, cacheCodeValue)) {
            throw new ServiceException(ResponseCode.VERIFY_CODE_ERROR);
        }

        stringRedisTemplate.delete(cacheKey);
    }

    private void validatorMobile(String username) {
        if (StrUtil.isBlank(username) && !Validator.isMobile(username)) {
            throw new ServiceException(ResponseCode.MOBILE_NOT_LEGAL);
        }
    }

}
